// Copyright (c) 2025 Zhou Yiyan
// Dynamic World Generation Library is licensed under Mulan PSL v2.
// You can use this software according to the terms and conditions of the Mulan PSL v2.
// You may obtain a copy of Mulan PSL v2 at:
//          http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.

// Package worldgen provides a dynamic world generation library for 2D games.
// It offers configurable, deterministic terrain generation with support for
// difficulty scaling and seamless chunk transitions.
package worldgen

import (
	"math/rand"

	"gitee.com/sweetiron/world-gen/internal/worldgen"
	"github.com/aquilax/go-perlin"
)

// ChunkCoord represents the unique coordinates of a map chunk.
type ChunkCoord = worldgen.ChunkCoord

// TileType represents the type of a single tile on the map.
type TileType = worldgen.TileType

// ChunkData represents the tile data for a single chunk.
type ChunkData = worldgen.ChunkData

// Generator is the core interface for world generation algorithms.
type Generator = worldgen.Generator

// NeighborProvider is a function type for querying neighboring chunk data.
type NeighborProvider = worldgen.NeighborProvider

// Constants for tile types and default values.
const (
	TileTypeEmpty    = worldgen.TileTypeEmpty
	TileTypeGrass    = worldgen.TileTypeGrass
	TileTypeWater    = worldgen.TileTypeWater
	TileTypeForest   = worldgen.TileTypeForest
	TileTypeMountain = worldgen.TileTypeMountain
	TileTypeDesert   = worldgen.TileTypeDesert
	TileTypeLava     = worldgen.TileTypeLava

	ChunkSize         = worldgen.ChunkSize
	DefaultDifficulty = worldgen.DefaultDifficulty
	DefaultSimilarity = worldgen.DefaultSimilarity
)

// NewNoiseGenerator creates a new noise-based world generator with the given seed.
func NewNoiseGenerator(seed int64) Generator {
	return &NoiseGenerator{seed: seed}
}

// TileTypeString returns a human-readable representation of the TileType.
func TileTypeString(t TileType) string {
	return worldgen.TileType(t).String()
}

// NoiseGenerator implements worldgen.Generator using Perlin noise.
type NoiseGenerator struct {
	seed int64
}

// GenerateChunk generates a chunk of terrain using Perlin noise.
func (g *NoiseGenerator) GenerateChunk(coord ChunkCoord,
	difficulty, similarity float64, neighbors map[ChunkCoord]ChunkData) (ChunkData, error) {
	seed := g.seed

	// Create deterministic random source from seed + chunk coordinates
	randSource := rand.New(rand.NewSource(g.getChunkSeed(seed, coord)))

	// Initialize Perlin noise with deterministic parameters
	p := perlin.NewPerlin(2.0, 2.0, 3, randSource.Int63())

	chunk := make(ChunkData, ChunkSize)
	for y := 0; y < ChunkSize; y++ {
		chunk[y] = make([]TileType, ChunkSize)
		for x := 0; x < ChunkSize; x++ {
			// Calculate global coordinates
			globalX := float64(coord.X*ChunkSize + x)
			globalY := float64(coord.Y*ChunkSize + y)

			// Get noise value (range: ~[-1.0, 1.0])
			noiseValue := p.Noise2D(globalX/50.0, globalY/50.0)

			// Adjust based on difficulty
			adjustedValue := g.adjustForDifficulty(noiseValue, difficulty)

			// Map to terrain type
			chunk[y][x] = g.noiseToTerrain(adjustedValue)
		}
	}

	// Apply similarity blending with neighbors if provided
	if similarity > 0 && len(neighbors) > 0 {
		worldgen.BlendEdge(chunk, neighbors, similarity, randSource)
	}

	return chunk, nil
}

// getChunkSeed creates a deterministic seed for this specific chunk.
func (g *NoiseGenerator) getChunkSeed(globalSeed int64, coord ChunkCoord) int64 {
	// Combine global seed with chunk coordinates for deterministic per-chunk randomness
	return globalSeed + int64(coord.X)*131071 + int64(coord.Y)*524287
}

// adjustForDifficulty modifies noise value based on difficulty coefficient.
func (g *NoiseGenerator) adjustForDifficulty(noiseValue, difficulty float64) float64 {
	// More aggressive difficulty adjustment:
	// - Low difficulty: bias toward positive values (safe terrain)
	// - High difficulty: bias toward negative values (dangerous terrain)

	if difficulty < 0.3 {
		// Easy mode: shift toward positive (grass, forest, water)
		return noiseValue + (0.3 - difficulty)
	} else if difficulty > 0.7 {
		// Hard mode: shift toward negative (lava, mountains, desert)
		return noiseValue - (difficulty - 0.7)
	}

	// Normal mode: minimal adjustment
	return noiseValue
}

// noiseToTerrain maps a noise value to a specific terrain type.
func (g *NoiseGenerator) noiseToTerrain(value float64) TileType {
	// Normalize value to [-1.0, 1.0] range for consistent mapping
	normalizedValue := value
	if normalizedValue < -1.0 {
		normalizedValue = -1.0
	} else if normalizedValue > 1.0 {
		normalizedValue = 1.0
	}

	switch {
	case normalizedValue < -0.7:
		return TileTypeLava
	case normalizedValue < -0.4:
		return TileTypeMountain
	case normalizedValue < -0.1:
		return TileTypeDesert
	case normalizedValue < 0.2:
		return TileTypeGrass
	case normalizedValue < 0.5:
		return TileTypeForest
	case normalizedValue < 0.8:
		return TileTypeWater
	default:
		return TileTypeEmpty
	}
}
